<?php
namespace service\address;

use artisan\http;

class AmapGeoQuery
{
    /**
     * api key
     * @var string
     */
//    const API_KEY = '913826dfa70cf0bf04e334cf4ca2fa0e';
    const API_KEY = '3049153a08dcaf615fe9dcd90a8a7f34';

    const API_OUTPUT_FORMAT = 'json';
    const API_BATCH_CONFIG = 'true';

    /**
     * request max size
     * @var integer
     */
    const MAX_BATCH_SIZE = 10;

    /**
     * API uri
     *
     * @var string
     */
    const GEO_API_URI = 'http://restapi.amap.com/v3/geocode/geo';

    const BATCH_API_URI = 'http://restapi.amap.com/v3/batch';

    /**
     * store the be processed original data
     *
     * @var array
     */
    private $originalData;

    /**
     * sliced chunk data
     *
     * @var array
     */
    private $chunkData;

    /**
     * processed data
     *
     * @var array
     */
    private $processedData = [];

    /**
     * @var array
     */
    private $addressIndexProcessedData = [];

    /**
     *
     * @var array
     */
    private $sortedData = [];

    private $formattedAddresses;

    private $lastQueryAddresses;

    private $defaultCity;

    /**
     * constant frequency use letter
     */

    const KEY_PROVINCE = 'province';
    const KEY_CITY = 'city';
    const KEY_ADDRESS = 'address';


    const REQUEST_TYPE_GEO = 1;
    const REQUEST_TYPE_BATCH = 2;

    /**
     * AmapGeoQuery constructor.
     *
     * @param array $data
     */
    public function __construct(array $data = []){
        $this->originalData = $data;
    }

    /**
     * @param array $data
     * @return array
     */
    public function process(array $data = [], $batchSize = 1)
    {
        if($data = $this->initData($data)){
            $chunkData = $this->dataSplit($data, $batchSize);
            foreach ($chunkData as $addresses){
                $this->lastQueryAddresses = $addresses;
                if($ret = http::get($this->buildRequestUri($addresses))){
                    $this->returnProcessor($ret, $addresses);
                }
            }
        }
        return $this->processedData;
    }


    /**
     * @param array $data
     * @param int $batchSize
     * @return array
     */
    public function multiThreadProcess(array $data = [], $batchSize = 1)
    {
        if($data = $this->initData($data)){
            $chunkData = $this->dataSplit($data, $batchSize);
            $urls = [];
            foreach ($chunkData as $key => $addresses){
                $this->lastQueryAddresses = $addresses;
                $urls[$key] = $this->buildRequestUri($addresses);
            }
            if(($return = http::mutiGet($urls)) && is_array($return)){
                foreach ($return as $k => $item) {
                    $this->returnProcessor($item, $chunkData[$k]);
                }
            }
        }
        return $this->processedData;
    }

    /**
     * @param string $city
     * @param array $data
     * @param int $batchSize
     * @return array
     */
    public function citySortedAddressProcess($city ,array $data = [], $batchSize = 1)
    {
        if(empty($city) || ($data = $this->setDefaultCity($city)->initData($data))){
            foreach ($data as $datum) {
                if(!is_array($datum)){
                    continue;
                }
                $chunkData = $this->dataSplit($datum, $batchSize);
                foreach ($chunkData as $addresses){
                    $this->lastQueryAddresses = $addresses;
                    if($ret = http::get($this->buildRequestUri($addresses))){
                        $this->returnProcessor($ret, $addresses);
                    }
                }
            }
        }
        return $this->processedData;
    }

    /**
     * @param array $data
     * @param int $batchSize
     * @return array
     */
    private function initData(array $data = [], $batchSize = 1)
    {
        count($data) && $this->originalData = $data;
        $data = $this->formatAddress($data);
        $this->getDefaultCity() and $data = $this->sortDataByCity($this->originalData);
        return $this->sortedData = $data;
    }

    /**
     * @return mixed
     */
    public function getFormattedAddress()
    {
        return $this->formattedAddresses;
    }

    /**
     * @return string
     */
    public function getDefaultCity()
    {
        return $this->defaultCity;
    }

    /**
     * @param $defaultCity
     * @return $this
     */
    public function setDefaultCity($defaultCity)
    {
        $defaultCity and is_string($defaultCity) and $this->defaultCity = $defaultCity;
        return $this;
    }

    /**
     * @param array $params
     * @return string
     */
    private function buildRequestUri(array $params)
    {
        return static::GEO_API_URI.'?'.$this->buildRequestParams($params);
    }

    /**
     * @param null|string $key
     * @return array
     */
    private function getDefaultParams($key = null)
    {
        return [
            'key' => $key ?: static::API_KEY,
            'output' => static::API_OUTPUT_FORMAT,
            'batch' => static::API_BATCH_CONFIG,
        ];
    }



    /**
     * @param array $data
     * @return string
     */
    private function buildRequestParams(array $data)
    {
        $address = implode('|', array_column($data, self::KEY_ADDRESS));
        return http_build_query(($city = $this->getDefaultCity() ? compact('city', 'address') : compact('address')) + $this->getDefaultParams());
    }

    /**
     * @param array $data
     * @return array
     */
    private function formatAddress(array $data)
    {
        if(count($data)){
            foreach ($data as &$datum) {
                $datum[self::KEY_ADDRESS] = $datum[self::KEY_PROVINCE].$datum[self::KEY_CITY]. $datum[self::KEY_ADDRESS];
            }
        }
        return $data;
    }
    
    /**
     * @param array $data
     * @return array
     */
    private function sortDataByCity(array $data)
    {
        $citySortedData = [];
        if($data) {
            foreach ($data as $item) {
                $citySortedData[$item[static::KEY_CITY]][] = $item;
            }
        }
        return $citySortedData;
    }

    /**
     * @param array $size
     * @return array
     */
    private function dataSplit(array $data, $size = self::MAX_BATCH_SIZE)
    {
        return count($data) > $size ? array_chunk($data, $this->getBatchSize($size)) : [$data];
    }

    /**
     * @param integer $size
     * @return int
     */
    private function getBatchSize($size)
    {
        return !is_int($size) || $size < 1 || $size > static::MAX_BATCH_SIZE ? static::MAX_BATCH_SIZE : $size;
    }

    /**
     * @param string $json
     * @param array $addresses
     */
    private function returnProcessor($json, array $addresses)
    {
        if($json && ($jsonArr = json_decode($json, true)) && json_last_error() === JSON_ERROR_NONE && !empty($jsonArr['geocodes'])) {
            foreach ($jsonArr['geocodes'] as $k => $item) {
                $address = $addresses[$k];
                $this->formattedAddresses[] = $item + ['original_address' => $address];
                $this->addressIndexProcessedData[$address['address']] = [
                    'waybillNo' => $address['waybill_no'],
                    'geo' => [
                        'address' => $address['address'],
                        'formatted_address' => $item['formatted_address'],
                        'city' => $item['city'],
                        'district' => $item['district'],
                        'location' => $item['location'],
                        'level' => $item['level'],
                    ],
                ];
            }
            foreach ($this->lastQueryAddresses as $lastQueryAddress) {
                $this->processedData[] = empty($this->addressIndexProcessedData[$lastQueryAddress['address']]) ? $lastQueryAddress : $this->addressIndexProcessedData[$lastQueryAddress['address']];
            }
        }
    }
}